package br.org.bertol.mestrado.engine.optimisation.pso;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import org.apache.log4j.Logger;

import br.org.bertol.mestrado.Verify;
import br.org.bertol.mestrado.engine.optimisation.MetaHeuristica;
import br.org.bertol.mestrado.engine.optimisation.ParetoRepository;
import br.org.bertol.mestrado.engine.optimisation.moo.ParetoDominace;
import br.org.bertol.mestrado.engine.optimisation.sls.IteratedLocalSearch;

public class ParticleSwarmOptimization extends MetaHeuristica<Particle> {

    private final TypeMovement                 tipoMovimento;

    protected final ParetoRepository<Particle> paretoRepository;

    public ParticleSwarmOptimization(final Verify verify,
        final List<Particle> swarm, final TypeMovement tipoMovimento,
        final long numIteracoes, final int depth, final int repositorySize) {
        super(verify, numIteracoes);

        this.listaPareto.addAll(swarm);

        this.tipoMovimento = tipoMovimento;

        paretoRepository = new ParetoRepository<Particle>(this.verify, depth,
                repositorySize);

    }

    @Override
    public final void evaluate() throws Exception {
        initSwarm();

        for (int kIteration = 0; kIteration < numIteracoes; kIteration++) {
            evaluateSwarmParticle(kIteration);
        }
    }

    /**
     * Rezalia uma iteração do enxame
     * @param k_iteration
     *            iteração atual
     * @throws Exception
     */
    private void evaluateSwarmParticle(final int k_iteration) throws Exception {

        Particle gBest = null;

        // recupera melhor global
        if (verify.getObjectives().length == 1) {
            gBest = paretoRepository.getGbest();
        }

        for (final ListIterator<Particle> iterator = listaPareto.listIterator(); iterator
                .hasNext();) {

            final Particle particle = iterator.next();

            if (verify.getObjectives().length > 1) {
                gBest = paretoRepository.getGbest();
            }
            // atualiza gbest
            particle.updateGbest(gBest);

            // atualiza velocidade
            particle.updateVelocity(k_iteration);

            // virifica��o in�cio: clona
            Particle dummy = (Particle) particle.clone();

            // atualiza posi��o do clone
            tipoMovimento.execute(dummy);

            // verifica se � v�lido movimento
            verify.verifyStrongConstraint(dummy, true);

            // caso movimenot v�lido
            // executa
            if (dummy.isValidPath()) {

                final Particle newParticle = applyILS(particle);

                // atualiza posi��o
                tipoMovimento.execute(newParticle);
                // calcula novo peso da solu��o
                newParticle.evaluateConstraint(verify);

                if (paretoDominace.compare(newParticle.getHeight(), newParticle
                        .getHpbest()) != ParetoDominace.RIGHT_DOMINE) {
                    newParticle.updatePBest();
                }

                iterator.set(newParticle);

            }
        }

        // adiciona a part�cula no reposit�rio
        paretoRepository.insertInto((ArrayList<Particle>) listaPareto);
    }

    private void initSwarm() throws Exception {

        for (ListIterator<Particle> iterator = listaPareto.listIterator(); iterator
                .hasNext();) {

            final Particle particle = iterator.next();

            final Particle new_particle = applyILS(particle);

            // atualiza posi��o
            tipoMovimento.execute(new_particle);

            // calcula novo peso da solu��o
            new_particle.evaluateConstraint(verify);

            new_particle.updatePBest();

            iterator.set(new_particle);
        }

    }

    private Particle applyILS(final Particle particle) throws Exception {

        Logger.getLogger(getClass())
                .debug(
                       "Unidade pr� ILS ---------------------------- : "
                + particle);

        final IteratedLocalSearch<Particle> iteratedLocalSearch = new IteratedLocalSearch<Particle>(
                verify, particle, 50);

        iteratedLocalSearch.evaluate();

        final Particle new_Particle = iteratedLocalSearch.getBetterUnit();

        Logger.getLogger(getClass())
                .debug(
                       "Unidade p�s ILS ---------------------------- : "
                + new_Particle);

        if (paretoDominace.compare(new_Particle.getHeight(), particle
                .getHeight()) == ParetoDominace.LEFT_DOMINE) {
            new_Particle.update();
        }

        return new_Particle;
    }

    public final List<Particle> getSwarm() {
        return listaPareto;
    }

    public final List<Particle> getParetoFront() {
        return paretoRepository.getParetoBorder();
    }

    /**
     * @see ParetoRepository#getParetoBorder()
     * @return Lista de soluções não dominadas
     */
    public final List<Particle> getParetoBorder() {
        return paretoRepository.getParetoBorder();
    }
}